#include <windows.h>
#include <wininet.h>
#include <stdarg.h>

void PrintExA(bool e, LPCSTR s, size_t n);
void PrintExW(bool e, LPCWSTR s, size_t n);
void PrintfA(bool e, LPCSTR fmt, ...);
void PrintfW(bool e, LPCWSTR fmt, ...);

template<size_t N>
void PrintA(bool e, const char (&s)[N])
{
    PrintExA(e, s, sizeof(s) - 1);
}

template<size_t N>
void PrintW(bool e, const wchar_t (&s)[N])
{
    PrintExW(e, s, sizeof(s) - 1);
}

template<typename T>
struct BufferBase
{
    T *buffer;
    BufferBase(size_t n)
    {
        buffer = (T*)HeapAlloc(GetProcessHeap(), 0, n * sizeof(T));
    }
    ~BufferBase()
    {
        HeapFree(GetProcessHeap(), 0, buffer);
    }
};

using Buffer = BufferBase<char>;
using BufferW = BufferBase<wchar_t>;
using BufferT = BufferBase<TCHAR>;

#if defined(UNICODE) || defined(_UNICODE)
#   define PrintEx PrintExW
#   define Printf PrintfW
#else
#   define PrintEx PrintExA
#   define Printf PrintfA
#endif

#define Print(h, s) PrintEx(h, s, sizeof(s) - 1)

#ifndef _DEBUG
#   define ERR(fun, code) ((void)0)
#   define E(fun) ((void)0)
#else
#   define ERR(fun, code) ErrorMessage(TEXT(__FILE__), __LINE__, TEXT(__FUNCSIG__), TEXT(fun), code)
#   define E(fun)                     \
        do                             \
        {                              \
            DWORD dw = GetLastError(); \
            if (dw)                    \
                ERR(fun, dw);          \
        } while (0)
void ErrorMessage(LPCTSTR lpszFileName, DWORD dwLine, LPCTSTR lpszParentFunction, LPCTSTR lpszFunction, DWORD dwCode); // 输出错误信息
#endif


void PrintNetErr();

struct C_HINTERNET
{
    HINTERNET h;
    ~C_HINTERNET()
    {
        if (h && !InternetCloseHandle(h))
            E("InternetCloseHandle");
    }

};
#define INIT(p, fn) do {p.h = fn; if(!p.h) { E(#fn); return 1; } } while(0)
#define INITE(p, fn) do {p.h = fn; if(!p.h) { E(#fn); PrintNetErr(); return 1; } } while(0)

struct Args
{
    int argc;
    LPWSTR *argv;
    bool init()
    {
        argv = CommandLineToArgvW(GetCommandLineW(), &argc);
        if (!argv)
            E("CommandLineToArgvW");
        return !!argv;
    }
    ~Args()
    {
        LocalFree(argv);
    }
};

struct File
{
    HANDLE hFile;
    bool isFile;
    File() : hFile(INVALID_HANDLE_VALUE), isFile(false) {}
    bool Open(LPCWSTR filename)
    {
        if(!filename)
        {
            hFile = GetStdHandle(STD_OUTPUT_HANDLE);
            return true;
        }
        hFile = CreateFileW(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
        if(INVALID_HANDLE_VALUE == hFile)
        {
            E("CreateFileW");
            return false;
        }
        isFile = true;
        return true;
    }
    ~File()
    {
        if (isFile)
            CloseHandle(hFile);
    }
    void Write(LPVOID buffer, DWORD size)
    {
        WriteFile(hFile, buffer, size, &size, NULL);
    }
};

int wmain(int argc, wchar_t **argv)
{
    if (argc < 2 || argc > 3)
    {
        PrintfW(true, L"usage: %s url [output filepath]\n", argv[0]);
        return 1;
    }
    File file;
    if(!file.Open(argv[2]))
        return 1;
    C_HINTERNET open;
    INIT(open, InternetOpenA("", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0));
    C_HINTERNET urls;
    INITE(urls, InternetOpenUrlW(open.h, argv[1], NULL, (DWORD)-1, 0, 0));
    DWORD dwSize = 0;
    do
    {
        if (!InternetQueryDataAvailable(urls.h, &dwSize, 0, 0))
            E("InternetQueryDataAvailable");
        else
        {
            Buffer buffer(dwSize);
            DWORD dwRead;
            if (!InternetReadFile(urls.h, buffer.buffer, dwSize, &dwRead))
            {
                E("InternetReadFile");
                dwSize = 0;
            }
            else
                file.Write(buffer.buffer, dwRead);
        }
    } while (dwSize);
    if(argc == 2)
        PrintA(false, "\n");
}

void wmainCRTStartup()
{
    int r = 0;
    {
        Args args;
        if(args.init())
            r = wmain(args.argc, args.argv);
    }
    ExitProcess(r);
}

void PrintExA(bool e, LPCSTR s, size_t n) { WriteConsoleA(GetStdHandle(e ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE), s, n, NULL, NULL); }
void PrintExW(bool e, LPCWSTR s, size_t n) { WriteConsoleW(GetStdHandle(e ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE), s, n, NULL, NULL); }
void PrintfA(bool e, LPCSTR fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    LPSTR s;
    DWORD dwSize = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING, fmt, 0, 0, (LPSTR)&s, 0, &ap);
    if (s)
    {
        PrintExA(e, s, dwSize);
        LocalFree(s);
    }
    va_end(ap);
}
void PrintfW(bool e, LPCWSTR fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    LPWSTR s;
    DWORD dwSize = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING, fmt, 0, 0, (LPWSTR)&s, 0, &ap);
    if (s)
    {
        PrintExW(e, s, dwSize);
        LocalFree(s);
    }
    va_end(ap);
}

void PrintNetErr()
{
    DWORD dwLen = 0;
    DWORD dwError = 0;
    InternetGetLastResponseInfoA(NULL, NULL, &dwLen);
    Buffer s(dwLen + 1);
    InternetGetLastResponseInfoA(&dwError, s.buffer, &dwLen);
    PrintfA(true, "error: %1!lu!, buffer: %2\n", dwError, s.buffer);
}

#ifdef _DEBUG
void ErrorMessage(LPCTSTR lpszFileName, DWORD dwLine, LPCTSTR lpszParentFunction, LPCTSTR lpszFunction, DWORD dwCode)
{
    LPTSTR lpMsgBuf = NULL;
    LPTSTR lpDisplayBuf = NULL;
    DWORD dwSize = 0;

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS |
            FORMAT_MESSAGE_MAX_WIDTH_MASK,
        NULL,
        dwCode,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&lpMsgBuf,
        0, NULL);

#ifdef __cplusplus
    using va_list_array = va_list[];
#else
#define va_list_array (va_list[])
#endif

    if (lpMsgBuf)
        dwSize = FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_ARGUMENT_ARRAY |
            FORMAT_MESSAGE_FROM_STRING,
            TEXT("Filename:%1\n")
            TEXT("Line:%2!lu!\n")
            TEXT("Parent function:%3\n")
            TEXT("Function:%4\n")
            TEXT("Code:%5!lu!\n")
            TEXT("Description:%6\n"),
            0,
            0,
            (LPTSTR)&lpDisplayBuf,
            0,
            va_list_array{
                (va_list)lpszFileName,
                (va_list)(SIZE_T)dwLine,
                (va_list)lpszParentFunction,
                (va_list)lpszFunction,
                (va_list)(SIZE_T)dwCode,
                (va_list)lpMsgBuf
            }
        );

    // Display the error message and exit the process
    if (lpDisplayBuf && dwSize)
        WriteConsole(GetStdHandle(STD_ERROR_HANDLE), lpDisplayBuf, dwSize, NULL, NULL);

    if (lpMsgBuf)
        LocalFree(lpMsgBuf);
    if (lpDisplayBuf)
        LocalFree(lpDisplayBuf);
}
#endif